package com.jidolstar.astro.datetimes
{
	/**
	 * Julian Date(율리우스 적일)를 다루는 클래스
	 * <p>
	 * Julian Date(JD)는 -4712년 1월 1일 월요일 정오 UT(B.C. 4713년 1월 1일 월요일 정오 UT)을 시작으로 하루단위로 날을 계수한 값이다.
	 * Julian Date는 하루단위인 정수부분과 시간단위인 소수점 이하 부분을 포함한다.
	 * Julian Day는 Julian Day Number(JDN)로 사용한다. JD의 정수부분만을 지칭한다.
	 * 기억해야할 사항은 Julian Date는 항상 영국 Greenwich를 기점(경도 0도)으로 한다.
	 * </p>
	 * 
	 * @example 
	 * var jd:JulainDate;
	 * jd = new JulianDate( new Date(1997), null, CalendarKind.GREGORIAN );
	 * trace( jd.jd, jd.mjd, jd.cjd, jd.rjd );
	 * 
	 * @see 
	 * http://en.wikipedia.org/wiki/Julian_day
	 * http://www.bambooweb.com/articles/j/u/Julian_day.html
	 * http://scienceworld.wolfram.com/astronomy/JulianDate.html
	 * http://wwwmacho.mcmaster.ca/JAVA/CD.html
	 * http://www.hermetic.ch/cal_stud/jdn.htm
	 * 
	 * http://www.cv.nrao.edu/~rfisher/Ephemerides/times.html
	 * http://en.wikipedia.org/wiki/Universal_Time
	 * http://www.merlyn.demon.co.uk/daycount.htm#GDDC
	 * 
	 * @author Yongho,Ji (jidolstar@gmail.com, http://jidolstar.com)
	 */ 
	public class JulianDate
	{
		private var _jd:Number;
		private var _jdn:Number;
		private var _mjd:Number;
		private var _rjd:Number;
		
		private var isSetJDN:Boolean = false;
		private var isSetMJD:Boolean = false;
		private var isSetRJD:Boolean = false;
		
		/**
		 * JulianDate 클래스 생성자 
		 * @param value 숫자형태 JD관련 값, dateFormat에 따라서 값의 종류가 달라진다. 
		 * @param dateFormat JulianDateFormat.JD, JulianDateFormat.MJD, JulianDateFormat.CJD, JulianDateFormat.RJD 값중 하나가 된다.
		 */ 
		public function JulianDate( value:Number, dateFormat:String = "JD" )
		{
			switch( dateFormat )
			{
				case JulianDateFormat.JD:
					jd = value as Number;
					break;
				case JulianDateFormat.MJD:
					mjd = value as Number;
					break;
				case JulianDateFormat.RJD:
					rjd = value as Number;
					break;
				default:
					throw new ArgumentError("format parameter is wrong value");
					break;
			}
		}
		
		/**
		 * Julian Date(율리우스 적일)
		 * <p>Julian Date -4712년 1월 1일 월요일 정오 UT(B.C. 4713년 1월 1일 월요일 정오 UT)을 시작으로 하루단위로 날을 계수한 값이다. 
		 * Julian Day는 Greenwich에서 정오(12h UT)일때 정수값이 된다.</p>
		 * <p>약어로 JD라고 하며, Universial Time(UT)와 대응된다. 만약 Dynamical Time과 대응되면 JD가 아닌 JDE(Julain Ephemeris Day)라 해야한다.</p>
		 * @see http://en.wikipedia.org/wiki/Julian_day
		 */ 
		public function get jd():Number
		{
			return _jd;
		}

		public function set jd( value:Number ):void
		{
			_jd = value;
			isSetMJD = false;
			isSetJDN = false;
			isSetRJD = false;
		}
		
		/**
		 * Julian Date로부터 hours만 뽑아냄 [0h,24h) 
		 */ 
		public function get hours():Number
		{
			var jd:Number = Math.floor( this.jd - 0.5 ) + 0.5;
			return (this.jd - jd) * 24.0;
		}
		
		/**
		 * Julian day number
		 */ 
		public function get jdn():Number
		{
			if( isSetJDN )
				return _jdn;
			_jdn = Math.floor(_jd);
			isSetJDN = true;
			return _jdn; 	
		}
		
		public function set jdn( value:Number ):void
		{
			_jdn = Math.floor(value);
			jd = _jdn;
			isSetJDN = true;
		}
		
		/**
		 * Modified Julian Date(MDJ)
		 * <p>MJD는 정오가 아닌 자정에 시작하며 MDJ = JD - 2400000.5 로 계산되어 진다.</p> 
		 */
		public function get mjd():Number
		{
			if( isSetMJD )
				return _mjd;
			_mjd = jd2mjd( _jd );
			isSetMJD = true;
			return _mjd;
		}

		public function set mjd( value:Number ):void
		{
			_mjd = value;
			jd = mjd2jd( _mjd );
			isSetMJD = true;
		}
		
		/**
		 * Reduced Julian Date
		 * <p>천문학자들에 의해 사용되며 1858-11-16 12:00(화요일)을 기준으로 하는 적일이다. 
		 * Julian Date(JD)에서 2,400,000을 뺀 값이다.
		 * </p>
		 */ 
		public function get rjd():Number
		{
			if( isSetRJD )
				return _rjd;
			_rjd = jd2rjd( _jd );
			isSetRJD = true;
			return _rjd;
		}
		
		public function set rjd( value:Number ):void
		{
			_rjd = value;
			jd = rjd2jd( _rjd );
			isSetRJD = true;
		}
		
		/**
		 * Julian Date의 clone() 만듬
		 */ 
		public function clone():JulianDate
		{
			return new JulianDate( _jd, JulianDateFormat.JD );
		}
		
		/**
		 * Julian Date 비교 
		 */ 
		public function equal( julianDate:JulianDate ):Boolean
		{
			if( julianDate == null ) return false;
			if( this.jd == julianDate.jd ) return true;
			return false;
		}
		
		/**
		 */
		public function toString():String
		{
			return this.jd.toString();	
		}
		
		/**
		 * Calculation of the MJD from the JD 
		 * @param JD
		 * @return MJD
		 */ 
		static public function jd2mjd( jd:Number ):Number
		{
			return jd - 2400000.5;
		}
		
		/**
		 * Calculation of the JD from the MJD 
		 * @param MJD
		 * @return JD
		 */ 
		static public function mjd2jd( mjd:Number ):Number
		{
			return mjd + 2400000.5;
		} 
		
		/**
		 * Calculation of the RJD from the JD 
		 * @param JD
		 * @return RJD
		 */ 		
		static public function jd2rjd( jd:Number ):Number
		{
			return jd - 2400000;
		}
		
		/**
		 * Calculation of the JD from the RJD 
		 * @param RJD
		 * @return JD
		 */ 
		static public function rjd2jd( rjd:Number ):Number
		{
			return rjd + 2400000;
		}
		
		/**
		 * Calculation of the JD from the General Date Object 
		 * @param year 년도
		 * @param month 월(1~12)
		 * @param day 일(1~31)
		 * @param hour 시간(0~23)
		 * @param minute 분(0~59)
		 * @param seconds 초
		 * @param calendarType 
		 * 		달력 타입. 기본값은 null이다. 
		 * 		특별한 경우에 CalendarKind.GREGORIAN 또는 CalendarKind.JULIAN 를 설정한다.
		 * 		<ul>
		 * 		
		 * 			<li> null값을 설정하는 경우 자동으로 1582.10.04 이전은 Julian Calendar를 적용해 계산하고 1582.10.15 이후에는 Gregorian Calandar를 적용해 계산한다. 
		 * 		 		만약 1582.10.05부터  1582.10.14내의 날짜로 맞추면 ArgumentError가 발생한다.
		 * 			<li> CalendarKind.GREGORIAN로 설정하는 경우 날짜는 Gregorian Calendar로 간주하고 Julian Date를 계산한다. 
		 * 	 		<li> CalendarKind.JULIAN로 설정하는 경우Julian Calendar로 간주하고 Julian Date를 계산한다.
		 * 		</ul>
		 * @return Julian Date 값을 반환한다. 
		 * @see Astronomical Algorithms in Meeus P.61
		 */ 
		static public function YMDHMS2jd( year:int, month:int, day:int, hour:int=0, minute:int=0, seconds:Number=0, calendarType:String = null ):Number
		{
			// 1. Gregorian Calendar인지  Julian Calendar인지 판단한다.
			// 2. 1582.10.5 ~ 1582.10.14 이면 ArgumentError 발생 
			if( calendarType == null )
			{
				if( year > 1582 ) 
				{
					calendarType = CalendarType.GREGORIAN;
				} 
				else if( year < 1582 )
				{
					calendarType = CalendarType.JULIAN;
				}
				else 
				{
					if( month > 10 )
					{
						calendarType = CalendarType.GREGORIAN;
					}
					else if( month < 10 )
					{
						calendarType = CalendarType.JULIAN;
					}
					else
					{
						if( day >= 15 )
						{
							calendarType = CalendarType.GREGORIAN;
						}
						else if( day <= 4 )
						{
							calendarType = CalendarType.JULIAN;
						}
						else
						{
							throw ArgumentError( "The date from 1582.10.05 to 1582.10.14 can't use" );
						}
					}
				}
			}
			var A:Number;
			var B:Number;
			var C:Number;
			if( month < 2 ) 
			{
				month += 12;
				year--;	
			}
			
			//달력 종류에 따라 계산 방법이 다르다.
			switch( calendarType )
			{
				case CalendarType.GREGORIAN:
					A = Math.floor( year /100 );
					B = Math.floor( A / 4 );
					C = 2 - A + B;				
					break;
				case CalendarType.JULIAN:
					A = 0;
					B = 0;
					C = 0;
					break;
			}
			
			var Ds:Number = day + hour/24 + minute/1440 + seconds/86400;
			var E:Number = Math.floor( 365.25 * ( year + 4716 ) ); 
			var F:Number = Math.floor( 30.6001 * ( month + 1) );
			return C + Ds + E + F - 1524.5; 
		} 
		
		
		
	}
}